package cn.wizzer.app.crown.modules.services.impl;

import cn.hutool.core.io.IORuntimeException;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.wizzer.app.crown.commons.util.DateUtil;
import cn.wizzer.app.crown.commons.util.ScanParamsUtil;
import cn.wizzer.app.crown.modules.models.CrownConfig;
import cn.wizzer.app.crown.modules.models.constant.CrownConstant;
import cn.wizzer.app.crown.modules.models.constant.CrownPattern;
import cn.wizzer.app.crown.modules.models.constant.CrownRedisConstant;
import cn.wizzer.app.crown.modules.services.CrownConfigService;
import cn.wizzer.app.sys.modules.services.SysParamService;
import cn.wizzer.framework.base.Result;
import cn.wizzer.framework.base.exceptions.BizException;
import cn.wizzer.framework.base.service.BaseServiceImpl;
import cn.wizzer.framework.page.Pagination;
import cn.wizzer.framework.util.JSONUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.Times;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.springframework.data.redis.core.StringRedisTemplate;
import redis.clients.jedis.ScanParams;

import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


@IocBean(args = {"refer:dao"})
@Service(interfaceClass = CrownConfigService.class)
public class CrownConfigServiceImpl extends BaseServiceImpl<CrownConfig> implements CrownConfigService {

    public CrownConfigServiceImpl(Dao dao) {
        super(dao);
    }

    private static final Log log = Logs.get();

    @Inject
    private RedisService redisService;

    @Inject
    private StringRedisTemplate stringRedisTemplate;

    @Reference(retries = 0, timeout = 5000)
    @Inject
    private SysParamService paramService;

    /**
     * 匹配年月日 2020年1月1日
     */
    private static final Pattern Y_M_D = Pattern.compile("(20\\d{2}[年]\\d{1,2}[月]\\d{0,2}[日])");
    /**
     * 匹配过期具体时间 ~17:00:00)但不包含~和)
     */
    private static final Pattern H_M_S = Pattern.compile("(?<=~).+(?=\\()");


    @Override
    public void editDo(CrownConfig crownConfig) {
        this.update(crownConfig);
        // 修改账号信息时同时更新redis.要保留redis中账号的uid和cookie
        String configStr = redisService.get(CrownRedisConstant.CROWN_ACCOUNT + crownConfig.getUsername());
        if (Strings.isNotBlank(configStr)) {
            CrownConfig oldConfig = JSONUtil.parseStringToJavaBean(CrownConfig.class, configStr);
            crownConfig.setUid(oldConfig.getUid());
            crownConfig.setCookie(oldConfig.getCookie());
        }
        // realSite默认与site相同
        crownConfig.setRealSite(crownConfig.getSite());
        // 存入redis
        redisService.set(CrownRedisConstant.CROWN_ACCOUNT + crownConfig.getUsername(), JSONObject.toJSONString(crownConfig));
    }


    @Override
    public Pagination data(String searchKeyword, String[] searchDate, Integer pageNumber, Integer pageSize) {
        // 根据站点,用户名搜索
        Sql sql = Sqls.create("select * from crown_config $condition ");
        Sql countSql = Sqls.create("select count(1) from crown_config $condition ");
        Cnd cnd = Cnd.NEW();
        // 按账号用户名搜索
        if (Strings.isNotBlank(searchKeyword)) {
            searchKeyword = "%" + searchKeyword + "%";
            cnd.and("username", "like", searchKeyword);
        }
        // 按时间搜索
        if (ArrayUtils.isNotEmpty(searchDate)) {
            cnd.and("opAt", ">=", Times.d2TS(Times.D(searchDate[0])));
            cnd.and("opAt", "<=", Times.d2TS(Times.D(searchDate[1])));
        }
        sql.setCondition(cnd);
        sql.appendSourceSql("order by opAt desc");
        countSql.setCondition(cnd);
        return this.listPage(pageNumber, pageSize, sql, countSql);
    }

    @Override
    public void loginHg(CrownConfig crownConfig) {
        log.info("准备请求登录皇冠接口，并更新账号属性:" + crownConfig);
        String loginInfo = this.getCrownLoginInfo(crownConfig, CrownConstant.RE_TRY_TIMES);
        try {
            // 登录成功
            if (loginInfo.contains("200|100||")) {
                // 设置账号信息
                crownConfig.setUid(loginInfo.split("\\|")[3]);
                crownConfig.setCookie(this.getCookie(loginInfo));
                crownConfig.setLocked(false);
                crownConfig.setState("busy");
                crownConfig.setLogout(false);
                // 清除备注中错误信息
                crownConfig.setDisabled(false);
                crownConfig.setRemark(null);
            }
            // 登录不成功
            else {
                // 当error code为101时(账号被封)或105时(账号密码错误)，设置账号‘禁用状态’ 为禁用
                if (loginInfo.contains("error|101") || loginInfo.contains("error|105")) {
                    crownConfig.setDisabled(true);
                }
                // 登录时遇到皇冠维护进行处理
                else if (CrownConstant.MAINTAIN_INFO.equals(loginInfo)) {
                    this.checkMaintain(loginInfo, crownConfig);
                }
                crownConfig.setState("free");
                loginInfo = Strings.isBlank(loginInfo) ? "请求超时" : loginInfo;
                // 清除备注中错误信息
                crownConfig.setRemark("账号异常信息:" + loginInfo);
                // 账号异常发邮件
                paramService.sendEmail("3412995626@qq.com", "皇冠账号登录失败",
                        "账号：" + crownConfig.getUsername() + "，具体信息:" + loginInfo);
            }
        } catch (BizException e) {
            log.error(e.getRemand());
            paramService.sendEmail("3412995626@qq.com", "皇冠账号登录失败",
                    "账号：" + crownConfig.getUsername() + "，具体信息:" + loginInfo);
            // 继续抛出异常，后台controller部分有拦截处理，让后台提示信息可以显示
            throw new BizException(e.getRemand());
        } finally {
            crownConfig.setOpAt(System.currentTimeMillis() / 1000);
            // 存入redis
            redisService.set(CrownRedisConstant.CROWN_ACCOUNT + crownConfig.getUsername(), JSONObject.toJSONString(crownConfig));
            // 更新账号状态
            this.update(crownConfig);
        }
    }

    @Override
    public void loginInit(List<CrownConfig> list) {
        log.info("HG账号登录初始化");
        for (int i = 0; i < list.size(); i++) {
            this.loginHg(list.get(i));
        }
    }


    private int index;

    @Override
    public synchronized CrownConfig syncOne(String type) {
        // 如果在维护中，return
        String redisLock = redisService.get(CrownRedisConstant.CROWN_MAINTAIN);
        if (Strings.isNotBlank(redisLock)) {
            log.info("syncOne 获取账号失败，皇冠正在维护");
            return null;
        }
        boolean hasOne = false;
        log.info("线程安全获取账号任务：" + type);
        // 从redis中获取可用账号集合
        List<CrownConfig> configs = this.usableAccounts();
        // 如果可用账号集合为空并且所有账号uid都是空，则初始化登录所有账号
        if (CollectionUtils.isEmpty(configs)) {
            List<String> accountKeys = this.getAllAccountKeys();
            List<CrownConfig> collect = stringRedisTemplate.opsForValue()
                    .multiGet(accountKeys)
                    .stream()
                    .map(configStr -> (CrownConfig) JSONUtil.parseStringToJavaBean(CrownConfig.class, configStr))
                    .filter(config -> Strings.isBlank(config.getUid()))
                    .collect(Collectors.toList());
            if (collect.size() == accountKeys.size()) {
                this.loginInit(collect);
            }
            // 判断如果初始化登录成功，则轮询返回一个账号
            configs = this.usableAccounts();
            if (CollectionUtils.isEmpty(configs)) {
                log.error("执行‘" + type + "’任务。可用账号个数为0");
            } else {
                hasOne = true;
            }
        } else {
            hasOne = true;
        }
        // 如果有可用账号进行轮询登录
        if (hasOne) {
            if (index <= 0) {
                index = configs.size();
            }
            CrownConfig crownConfig = configs.get(--index);
            crownConfig.setType(type);
            return crownConfig;
        } else {
            return null;
        }
    }

    /**
     * 获取可用账号集合(账号未被禁用、remark为空、uid不为空)
     *
     * @return
     */
    private List<CrownConfig> usableAccounts() {
        List<String> accountKeys = this.getAllAccountKeys();
        return stringRedisTemplate.opsForValue()
                .multiGet(accountKeys)
                .stream()
                .map(configStr -> (CrownConfig) JSONUtil.parseStringToJavaBean(CrownConfig.class, configStr))
                .filter(config -> Strings.isBlank(config.getRemark()) && Strings.isNotBlank(config.getUid()) && !config.isDisabled())
                .collect(Collectors.toList());
    }


    /**
     * 获取redis中所有账号key(HG#AC#UN:pop102)的集合
     *
     * @return
     */
    private List<String> getAllAccountKeys() {
        String keys = CrownRedisConstant.CROWN_ACCOUNT + "*";
        ScanParams scanParams = ScanParamsUtil.get().match(keys);
        // 从redis中获取logout为false & uid不为空 & disable为false的账号集合
        return redisService.scan("0", scanParams).getResult();
    }


    @Override
    public void accountCheck() {
        // 如果在维护中，return
        String redisLock = redisService.get(CrownRedisConstant.CROWN_MAINTAIN);
        if (Strings.isNotBlank(redisLock)) {
            log.error("accountCheck return，皇冠官网正在维护");
            return;
        }
        // 获取redis中disable为false并且(uid为空或remark不为空)的账号进行登录
        List<String> accountKeys = this.getAllAccountKeys();
        List<CrownConfig> configs = stringRedisTemplate.opsForValue()
                .multiGet(accountKeys)
                .stream()
                .map(configStr -> (CrownConfig) JSONUtil.parseStringToJavaBean(CrownConfig.class, configStr))
                .filter(config -> !config.isDisabled() && (Strings.isBlank(config.getUid()) || Strings.isNotBlank(config.getRemark())))
                .collect(Collectors.toList());
        for (CrownConfig config : configs) {
            this.loginHg(config);
        }
    }

    @Override
    public void release(CrownConfig crownConfig) {
        // 获取账号，判断是否为空，以防账号已经被清除
        String crownStr = redisService.get(CrownRedisConstant.CROWN_ACCOUNT + crownConfig.getUsername());
        log.info("finally释放账号准备:" + crownStr + "|账号名:" + crownConfig.getUsername());
        if (Strings.isNotBlank(crownStr)) {
            // 解锁账号
            log.info("解锁账号:" + crownConfig);
            // 需要重新解析一次json再set，不然会覆盖账号异常的remark信息为""。
            crownConfig = JSONObject.parseObject(crownStr, CrownConfig.class);
            crownConfig.setLocked(false);
            redisService.set(CrownRedisConstant.CROWN_ACCOUNT + crownConfig.getUsername(), JSONObject.toJSONString(crownConfig));
        }
    }

    @Override
    public String getCrownData(String tailUrl, CrownConfig config, int reTry) {
        String body = "";
        // 把皇冠地址拼接到url头部
        String url = config.getRealSite() + tailUrl;
        log.info("准备发送http请求,地址:" + url + ",使用账号:" + config.getUsername());
        try {
            HttpResponse res = HttpUtil.createGet(url)
                    .setReadTimeout(5000)
                    .cookie(config.getCookie())
                    .setConnectionTimeout(5000)
                    .execute();
            // 如果是404直接设置维护
            int code = res.getStatus();
            if (404 == code || 500 == code) {
                return CrownConstant.MAINTAIN_INFO;
            } else {
                body = res.body();
                // 检测新地址
                if (getNewDomain(body, config)) {
                    reTry = CrownConstant.RE_TRY_TIMES;
                    return this.getCrownData(tailUrl, config, --reTry);
                }
            }
        } catch (IORuntimeException | HttpException e) {
            log.error("爬取数据失败，URL：" + url + "重试次数剩余：" + reTry);
            log.error("异常具体信息：" + e);
            if (reTry > 1) {
                return this.getCrownData(tailUrl, config, --reTry);
            }
        }
        return body;
    }


    /**
     * 请求皇冠登录接口，返回response body。'请求'成功返回'登录'信息(包括登录成功和失败)，'请求'不成功内部处理抛出BizException。
     *
     * @return 新版本成功返回的信息：200|100||co1ev95dm23448051l429570b0|4|zh-cn|23448051。
     * 之前版本成功返回的信息：200|100||6dvq5u8km23362301l176767b0|4|en-us
     * 失败：error|105|Invalid username or password. Please try again.||||
     */
    private String getCrownLoginInfo(CrownConfig config, int reTry) {
        String body = "";
        // 把皇冠地址拼接到url头部
        String url = config.getRealSite() + "/app/member/new_login.php";
        try {
            HttpResponse res = HttpUtil.createPost(url)
                    .setReadTimeout(5000)
                    .setConnectionTimeout(5000)
                    .form(Result.createMap(
                            "username", config.getUsername(),
                            "passwords", config.getPasswords(),
                            "langx", CrownConstant.LANGX,
                            "auto", "DADBDE",
                            "blackbox", "04003ZWEKk/A6R4pIfq2LLtL/Y0ii/xnum/zfPpuNJz0@2UCP3I/gtJSIOMr0YV5w5CL23AfbTn4cXXp/GQAAQUiaUEFfeFxZ4mShd01Jpb3ubmMzJtSWnIRBcEv6aX0T5Ew5sdrzcINkHqSqiU/wlowqKhMe4b9SwNjI/GgQmgZKntdzAoqCedUsdECyaln3bcDds/15Sn7/vmnHbzmopDdqilCz8QbOZkv6m6klcyz3h9sZFkfTsFfRirgS3ww3o6oa0Yy5JnHYpPbcB9tOfhxden8ZAABBSJpQQV94XFniZKF3TUmlve5uYzMm1JachEFwS/ppfRPkTDAqvQx/6iBZqmv4an1VB05SuO@gWkTCwWmUGFXcIY@JCCsEfnG/KSZA2uWbNuqgNN0ic5Zo9xviVx@4oTf94nSHOzjyi9frDdHtjbiPupS5MgwJY/1kvy4dZTG3Q1JuSFJp5PUg5jUaSdzBR@/B5fnRVjCPy8LXfsXNXdgvFjjoIbCmzQNSG7Ks9NGSaOBqmcYK91lVuo9zUJXs9LG5o/h3EmQjk2mIgfaCnACZwym27Yc/4DNEoi/Znv4Scx8Lgvp38ec2SH8oDeShPod2qf1@Lx@m25PhX7nsueaw/fwdi38MA3Wh1WoJgVSk0pcKUTCDJsS50gPlqoMQcHHQxqCfY9jE3aBXTo@WO5tA7Jn6V3EY0bKFRRRi6gqYs3uxZxycMHZCcHjpabC3SvgKdF2CjlU0y9oNd@sjcxpJiuDOyEioquMr6B3kRssV7u80BzgiEms7PTw/N/7HinDpb16xsYSmrsKhFi8VDdLGBfK9PH/LgnAtMTg3G/gqjChOr22Liq0dKm2qxC/CwsbD4fITMj6Y82LrlflYU9iUMb03X6mAJ3ZTHu23WaFMcpDuPKpNYuU/h@Gc0nbtw2dg9JBSzKz6zFbyZbyxPMQMv7YZ9mxtLKX2QfncSs2NHNZzALDmwR0X8hdybzQxh/TnxJBQEdu2XYvr46icoEUIBl9dJXAAYR3cfgvN4YbjEiJxk3o0NRac5lg3twTidneZ5Wedvp3EOKiSx6OC0ceqoTbyG/m3Zqxt3w1Bn6LxoWOnZu64rr07LwRGXZpyc7oQIV63xadNE2jqb6o3IAEdbB@xBfwKMcRIoeJtvb3n2nbY@LqW9TJIZnh2AqQz2ECMnu9PHs6jytXtWZXqLz@1vlqUAr9dE2jcfl004eOzsc7kIv/@WLydhuyoumL3ii@Ww3rCXbEp/bUW02sjcxpJiuDOyEioquMr6B3kRssV7u80BzgiEms7PTw/N/7HinDpb16xsYSmrsKhFi8VDdLGBfK9PH/LgnAtMTg3G/gqjChOr22Liq0dKm2qxC/CwsbD4fITMj6Y82LrlflYU9iUMb03QtuHmc6Pe7cNepEKIoJSuxTQRdWXuxQ4Y8yy5y5ZwajkNrAXEvbWw7IauRtCQwBH0aLtD0@WrHlnMzxppthvREuhS7iz11IvZjrRWorM//X/xMjgsV7ToOziBBkVbZqNGEW8KDR4/2uA/ApDIEdsQUDI1VMX2PdSeEqq4eCAOjtPATNf6iuaj2FxMVKFbmzpHhQ6gBTnvlY4soW@cvAX9Ke/f8Oh4TTWbu3zph7s@l3rV2n5B23zy766LCoTBcDeGUrflojoeVra3zZFjQ1S1ZLBVfzGCP9Lk8uRdqE9AoiXHRDE85543lIwnljgdA1Ni6kyHGYxUdG03Bv7XW6TQ3OSBUtMqwehBjhwdIJ7Z0b0AiBlKLLa8MYUZefgV6xf8TO0HCGLI3c2/Ec1nc2SuG3bqHgDgxVlp/j9BzcN5DsgJnjRx@vsOorTTx9U6HMzopnWvKr4sHNDgmHemJYbR@Vt3glIojgnF7Wm4YyDj4UjshZ0rqSV6fjrU2uMZpvPCJb6G6xN7/FKtg@kposZxTTA3NlZpwZHwG6evRnYK5wiJV7nzd2/7uguthOltRUqk9WNmmL2DDGuYcGPAIu@rNAODufHJqr7kPTgIDbxErbwv@3XRHq5alw8cotJtaEQvwybqjrB1OyDULP8JEdErpJ2djimcsRW/rToP4ROCwHD/L3Cy5EKGkCHj6/6X6ZOYCLbhX1QGvDmFWJhaS@nO3AIYH0eNQsHpKpuX6292jIMMn58mjJbt8m3SA@X5tUXWqGNIFSUvgQPu7LvhdNsBp5gnDd35qVzErf55fu3hNLzzWNVOeCJtcSM37vJ/3HSsT5bgblQ93nKam5HRldK7XtYzCVArZUjnTY59DKt9guxKylBasd7BknDh9tzMPX7oasLBL3R@bXbf03vGH/e6aOJTHsxHJ2dJ4LFukLziJ10JaafNKUyQAP4q/t6imMryjTr/MEe30kohON5IyEOsM5Vq9QQc5VJmls4ge@uSin3T0I7uSmqz2B1zgRopF2Fq@R62Y2lnl1lfEpW@XqMo7OEuevr29xE2QQ7BnC97Z6/63aIRZb@39AudaF09PqouM4R19rpCteYHJ@EErYypgwZQRLJscjp8zRzd5WQN2@TfPq0jXs@f9Bdf697Ugld95g0MuGO7g9lyB8eIFhAfxGGslqhJqtFLhdLPZN2U/V43Z6N6jflLrS98vuLMJDOJTLzztx@HCoL/cKkiOb9Oot4OlPDHSiwbEfVk9EjAzqJqKR0TY3@IFKO/MIU5@sag==;0400K6s@Dab1SY/jK9GFecOQi1BVl25epfehDXyMPqPOb5lUSECmkrue0eFGj3AYcGX9QEbjE1oj2ROYRf0KQApCj5RvFCMSbl5xeBTlywA9Ufm4B9Q/jTFhISpq4/eQIAw4Yg8CuMOt5MLq4WCiHMbq2Gi2QhUIPsgJeQP@Pbx6ME71CYi@TBqr3l0FdVHqnLzAJVZiEqaqvFa6iYFt@4h3FS6CSn6ti/@ouqK/tAtR4vIrKI/MdChDEXolW@Nhw66Ra0Yy5JnHYpNQVZduXqX3oQ18jD6jzm@ZVEhAppK7ntHhRo9wGHBl/UBG4xNaI9kTmEX9CkAKQo/AqvQx/6iBZqmv4an1VB05SuO@gWkTCwWmUGFXcIY@JCCsEfnG/KSZA2uWbNuqgNN0ic5Zo9xviVx@4oTf94nSHOzjyi9frDdHtjbiPupS5MgwJY/1kvy4dZTG3Q1JuSFJp5PUg5jUaSdzBR@/B5fnRVjCPy8LXfsXNXdgvFjjoIbCmzQNSG7Ks9NGSaOBqmcYK91lVuo9zUJXs9LG5o/h3EmQjk2mIgfaCnACZwym27Yc/4DNEoi/MyR8FVKQC94GDunxFH2UaWZ7@EnMfC4L6d/HnNkh/KA3koT6Hdqn9fi8fptuT4V@57LnmsP38HYt/DAN1odVqCYFUpNKXClEwgybEudID5aqDEHBx0Magn2PYxN2gV06PljubQOyZ@m5PmAeYr3Qk4uoKmLN7sWccnDB2QnB46Wmwt0r4CnRdgo5VNMvaDXfrI3MaSYrgzshIqKrjK@gd5EbLFe7vNAc4IhJrOz08Pzf@x4pw6W9esbGEpq7CoRYvFQ3SxgXyvTx/y4JwLTE4Nxv4KowoTq9ti4qtHSptqsQvwsLGw@HyEzI@mPNi65X5WFPYlDG9N1@pgCd2Ux7tt1mhTHKQ7jyqTWLlP4fhnNJ27cNnYPSQUsys@sxW8mW8sTzEDL@2GfZsbSyl9kH53ErNjRzWcwCw5sEdF/IXcm80MYf058SQUBHbtl2L6@OonKBFCAZfXSVwAGEd3H4LzeGG4xIicZN6NDUWnOZYN7cE4nZ3meVnnb6dxDioksejgtHHqqE28hv5t2asbd8NQZ@i8aFjp2buuK69Oy8ERl2acnO6ECFet8WnTRNo6m@qNyABHWwfsQX8CjHESKHibb2959p22Pi6lvUySGZ4dgKkM9hAjJ7vTx7Oo8rV7VmV6i8/tb5alAK/XRNo3H5dNOHjs7HO5CL//li8nYbsqLpi94ovlsN6wl2xKf21FtNrI3MaSYrgzshIqKrjK@gd5EbLFe7vNAc4IhJrOz08Pzf@x4pw6W9esbGEpq7CoRYvFQ3SxgXyvTx/y4JwLTE4Nxv4KowoTq9ti4qtHSptqsQvwsLGw@HyEzI@mPNi65X5WFPYlDG9N0Lbh5nOj3u3DXqRCiKCUrsU0EXVl7sUOGPMsucuWcGo5DawFxL21sOyGrkbQkMAR9Gi7Q9Plqx5ZzM8aabYb0RLoUu4s9dSL2Y60VqKzP/1/8TI4LFe06Ds4gQZFW2ajT5y8TCexVjPApRgik2Sgd2oMslCXFjnypyrm5GJQ9ngrUJO38Qf/1S68PWxu3p/gN4UOoAU575WOx2qc9SEAv/JPbJrpMW9pweSDnJhOCEZ7k4NpJntI@w8AAJxmdV6/KIMaiuhFfPkthtqe1SI6OVSwVX8xgj/S5PLkXahPQKIm7FzrkotkNE@mMUxBb6yYt7uV5sc1k2/FalrYc3sqh0I2E0o8g3ugsBgkGiZF1OxriQZO0TFs8mkCQuJR1yah5hv3HZCDBW5o6H2W0dl9Ai593iHMSpMHCcck2M1kUAiIEfSQGj2ggUSUpGCTU5RhBw4PPdU2VWjEuDAJWHSPRjmHQeoR5NbZ@ilBv9YOmvxqe055@C5iYxU0RtwDhofBXdmUpyniN/ebPvmC6tuGH2st1en5hFpfcx/ccubkmKi7nO294HFmI@@Em/zkTeIHTUmlOn4DSWtNXHsr8tyHfxnUeTpPnc@EU@JGY7Vn1SRV7SG@0yDPoKaXhY3ohTa2UcYLuNDCVJoxbt/WRTc7sB/O8cKOF6lTvy/GQUHG5Zv5laW2D3Jxb825NH/QhlbH/BwtbFYC@pHSJd0KR0yohboqRszoGFjcwLtncFKxWn8J/K@43kBr5edBVJnaWFT6ZwiSWl/fjlzik2t0m1j43Et6MIGnbg7d2rJNpdgMhJi5HedY0GhLj8eYOd9mrmYQtQA/0xnxZL3x54D/SGn@YEIG5fZTO8eWdoP5eBPQ@zTSyEmgN0xUu0U7oczh@iiXSHfBgfW9d1xH027Cj1QfqOHiqgVfPKC@ko06/zBHt9JKITjeSMhDrDOVavUEHOVSZUjgIk1i7l/d09CO7kpqs9gdc4EaKRdhavketmNpZ5dZXxKVvl6jKOzhLnr69vcRNkEOwZwve2ev@t2iEWW/t/LEEGnrvUda38ef4MbwAH11m/@gzhbSkY1/lN1QgF8OrWohxfHNCWC3eVw4hIJ1Uu4uvO69CC0WA@cCTtKfZNgH4KzkRoUXrbVAiAuqAPdeSYstKbZ0Ftu@kBWF7xgv6M"
                    ))
                    .execute();
            // 如果是404直接设置维护
            int code = res.getStatus();
            if (404 == code || 500 == code) {
                return CrownConstant.MAINTAIN_INFO;
            } else {
                body = res.body();
                // 检测新地址
                if (getNewDomain(body, config)) {
                    reTry = CrownConstant.RE_TRY_TIMES;
                    return this.getCrownLoginInfo(config, --reTry);
                }
            }
        } catch (IORuntimeException | HttpException e) {
            log.error("爬取数据失败，URL：" + url + "重试次数剩余：" + reTry);
            log.error("异常具体信息：" + e);
            if (reTry > 1) {
                return this.getCrownLoginInfo(config, --reTry);
            }
        }
        return body;
    }


    /**
     * 获取新地址
     *
     * @param body
     * @param config
     * @return
     */
    private boolean getNewDomain(String body, CrownConfig config) {
        if (body.contains("newdomain")) {
            Matcher m = CrownPattern.NEW_DOMAIN.matcher(body);
            if (m.find()) {
                String newDomain = m.group(0).replace("action='", "").replace("'", "");
                config.setRealSite(newDomain);
                redisService.set(CrownRedisConstant.CROWN_ACCOUNT + config.getUsername(), JSONObject.toJSONString(config));
                log.error("获取到新地址：" + newDomain);
                return true;
            } else {
                log.error("获取不到newdomain，body：" + body);
            }
        }
        return false;
    }


    /**
     * 处理登录皇冠返回的信息。组装cookie。该cookie并不是从http请求中获取到的。是根据自己测试成功后组装出来的。
     * <p>
     * http中皇冠cookie：Cookie: _ga=GA1.2.486058912.1600593143; OddType@23448047=H; _ga=GA1.3.486058912.1600593143; OddType@23448059=H; protocolstr=http; test=init; OddType@23448051=H; _gid=GA1.3.827717280.1605433634; cu=Y; gamePoint_23448051=2020-11-15%2A2%2A0; login_23448051=1605446428
     * 自己测试有用的部分：gamePoint_23448051=%2A2%2A0;
     * 有用的部分中gamePoint_后面的数字是登录成功后返回的，等号后面不加日期也可以。
     *
     * @param loginInfo
     * @return
     */
    private String getCookie(String loginInfo) {
        String[] split = loginInfo.split("\\|");
        if (split.length >= 6) {
            return "gamePoint_" + split[6] + "=%2A2%2A0;";
        } else {
            throw new BizException("组装cookie失败，登录信息按|分割后长度小于6：" + loginInfo);
        }
    }


    @Override
    public synchronized boolean checkLogout(String body, CrownConfig config) {
        // 多任务调用该方法，所以加锁。进方法重新查一遍账号，如果logout已经为true，return。
        String s = redisService.get(CrownRedisConstant.CROWN_ACCOUNT + config.getUsername());
        CrownConfig configNew = JSONObject.parseObject(s, CrownConfig.class);
        if (configNew.isLogout()) {
            return true;
        }
        if (body.contains(CrownConstant.LOGOUT_INFO)) {
            log.error(config.getUsername() + "被登出");
            config.setLogout(true);
            config.setLocked(true);
            config.setState("free");
            // 清除备注中错误信息
            config.setRemark("账号被登出");
            redisService.set(CrownRedisConstant.CROWN_ACCOUNT + config.getUsername(), JSONObject.toJSONString(config));
            // 更新账号状态
            this.update(config);
            return true;
        }
        return false;
    }

    @Override
    public boolean checkMaintain(String body, CrownConfig config) {
        // 维护时间
        long exTime = CrownRedisConstant.TIME_OUT_300_S;
        // 维护(不能登录只能访问首页)
        if (CrownConstant.MAINTAIN_INFO.equals(body)) {
            exTime = this.getExTime(config, exTime);
        }
        // 短期维护(能登录但是获取不到赛事列表)
        else if (CrownConstant.MAINTAIN_INFO_SHORT.equals(body)) {
            exTime = this.getShortExTime(config, exTime);
        }
        // 正常信息要return false
        else {
            return false;
        }
        // redis中设置维护flag
        redisService.set(CrownRedisConstant.CROWN_MAINTAIN, CrownRedisConstant.CROWN_MAINTAIN, "NX", "EX", exTime);
        return true;
    }


    /**
     * 获取短期维护时间
     *
     * @param config
     * @param exTime
     * @return
     */
    private long getShortExTime(CrownConfig config, long exTime) {
        // 请求http://66.133.86.54/app/member/showMaintain.php 获取维护时间
        try {
            String url = config.getRealSite() + CrownConstant.MAINTAIN_TIME_URL;
            log.error("准备请求维护页面获取维护时间,地址：" + url);
            String body = HttpUtil.createGet(url)
                    .setReadTimeout(5000)
                    .setConnectionTimeout(5000)
                    .execute()
                    .body();
            String[] split = body.split(",");
            if (8 == split.length) {
                // 得到维护时间
                Date date = DateUtil.parseDate(split[5], DateUtil.DATE_FORMAT_PATTERN.Y_M_D_H_M_S.PATTERN);
                if (null != date) {
                    long time = (date.getTime() - System.currentTimeMillis()) / 1000;
                    exTime = time > 0 ? time : exTime;
                }
            }
            log.error("设置盘口维护，body：" + body + "维护时间：" + exTime + "秒");
        } catch (IORuntimeException | HttpException e) {
            log.error("维护请求" + CrownConstant.MAINTAIN_TIME_URL + "异常：" + e.getMessage());
        }
        return exTime;
    }


    /**
     * 获取长期维护时间
     *
     * @param config
     * @return
     */
    private long getExTime(CrownConfig config, long exTime) {
        // 请求http://66.133.86.54/ 获取维护截止时间
        try {
            log.error("准备请求维护页面获取维护时间,地址：" + config.getRealSite());
            String body = HttpUtil.createGet(config.getRealSite())
                    .setReadTimeout(5000)
                    .setConnectionTimeout(5000)
                    .execute()
                    .body();
            // 爬年月日
            Matcher matcher = Y_M_D.matcher(body);
            if (matcher.find()) {
                String group = matcher.group();
                // 爬时间
                matcher = H_M_S.matcher(body);
                String dateStr = matcher.find() ? group + matcher.group() : "";
                // 转换成Date
                Date date = DateUtil.parseDate(dateStr, DateUtil.DATE_FORMAT_PATTERN.Y_M_D_H_M_S_CHINESE.PATTERN);
                if (null != date) {
                    long time = (date.getTime() - System.currentTimeMillis()) / 1000;
                    exTime = time > 0 ? time : exTime;
                }
            }
            log.error("设置盘口维护，body：" + body + "维护时间：" + exTime + "秒");
        } catch (IORuntimeException | HttpException e) {
            log.error("维护请求" + config.getRealSite() + "异常：" + e.getMessage());
        }
        return exTime;
    }
}
